<template>
  <!-- 左侧面板容器 -->
  <div class="left-panel">
    <!-- 标题 -->
    <h2>知识树管理</h2>

    <!-- 操作区域 -->
    <div class="operation-area">
      <el-input v-model="filterText" class="filter-input" placeholder="输入关键字过滤节点" clearable />
      <el-button type="primary" @click="addRootNode">添加根节点</el-button>
    </div>

    <el-tree 
      ref="treeRef" 
      class="knowledge-tree" 
      :data="treeData" 
      node-key="id" 
      :props="defaultProps"
      default-expand-all 
      draggable 
      :accordion="true" 
      :filter-node-method="filterNode" 
      :allow-drop="allowDrop"
      :allow-drag="allowDrag" 
      @node-click="handleNodeClick"
      @node-drag-start="handleDragStart"
      @node-drag-enter="handleDragEnter"
      @node-drag-leave="handleDragLeave" 
      @node-drag-over="handleDragOver"
      @node-drag-end="handleDragEnd" 
      @node-drop="handleDrop">
      <template #default="{ node, data }">
        <div class="custom-tree-node">
          <span>{{ node.label }}</span>
          <span class="node-actions">
            <el-icon class="node-action" :size="14" color="#409EFF" @click.stop="editNode(data)">
              <EditPen />
            </el-icon>
            <el-icon class="node-action" :size="14" color="#67C23A" @click.stop="addChildNode(data)">
              <CirclePlus />
            </el-icon>
            <el-icon class="node-action" :size="14" color="#F56C6C" @click.stop="removeNode(node, data)">
              <Delete />
            </el-icon>
          </span>
        </div>
      </template>
    </el-tree>

    <el-dialog v-model="dialogVisible" :title="dialogType === 'add' ? '添加节点' : '编辑节点'" width="30%">
      <el-form :model="nodeForm" label-width="80px">
        <el-form-item label="节点名称">
          <el-input v-model="nodeForm.title" placeholder="请输入节点名称" />
        </el-form-item>
        <el-form-item label="描述">
          <el-input v-model="nodeForm.description" type="textarea" placeholder="请输入描述" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span>
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="submitNodeForm">确定</el-button>
        </span>
      </template>
    </el-dialog>

    <el-dialog v-model="deleteDialogVisible" title="警告" width="500">
      <span>确定要删除节点 "{{ pendingDeleteNode?.title }}" 吗？删除后将无法恢复。</span>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="handleDeleteCancel">取消</el-button>
          <el-button type="primary" @click="handleDeleteConfirm">确定</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
/**
 * 导入Vue相关API
 * 
 * el-tree通过debug，发现draopNOde、draggingNode这种节点数据是有childNode（子节点有哪些）、parent（父节点是谁）、data（包含有具体的后端传来的数据，这个具体数据中有属性children、title、order、parentId）、label（和后端定义title是等价的）等属性的，可以直接用
 */
import { ref, reactive, onMounted, watch, nextTick } from 'vue'
/**
 * 导入Element Plus组件
 */
import { ElMessage, ElMessageBox, ElIcon } from 'element-plus'
/**
 * 导入Element Plus图标
 */
import { EditPen, CirclePlus, Delete } from '@element-plus/icons-vue'
/**
 * 导入知识树服务
 */
import knowledgeTreeService from '@/services/knowledgeTree'

/**树形数据源
 * 
 * @type {import('vue').Ref<Array<Object>>}
 */
const treeData = ref([])
/**树组件引用
 * 
 * @type {import('vue').Ref}
 */
const treeRef = ref()
/**过滤文本
 * 
 * @type {import('vue').Ref<string>}
 */
const filterText = ref('')
/**对话框显示状态
 * 
 * @type {import('vue').Ref<boolean>}
 */
const dialogVisible = ref(false)
/**删除对话框显示状态
 * 
 * @type {import('vue').Ref<boolean>}
 */
const deleteDialogVisible = ref(false)
/**待删除节点
 * 
 * @type {import('vue').Ref<Object|null>}
 */
const pendingDeleteNode = ref(null)
/**对话框类型（'add'或'edit'）
 * 
 * @type {import('vue').Ref<string>}
 */
const dialogType = ref('add')
/**当前操作节点
 * 
 * @type {import('vue').Ref<Object|null>}
 */
const currentNode = ref(null)
/**节点表单数据
 * 
 * @type {Object}
 * @property {string} title - 节点标题
 * @property {string} description - 节点描述
 * @property {string|null} parentId - 父节点ID
 */
const nodeForm = reactive({
  title: '',
  description: '',
  parentId: null
})
/**树形组件配置 从后台拿到的数据和el-tree的有些不同，在这里做一下映射，在模板里面会调用
 * 
 * @type {Object}
 * @property {string} children - 子节点字段名
 * @property {string} label - 显示文本字段名
 */
const defaultProps = {
  children: 'children',
  label: 'title'
}
/**监听过滤文本变化并应用过滤
 * 
 */
watch(filterText, (val) => {
  treeRef.value?.filter(val)
})
/**过滤节点方法
 * 
 * @param {string} value - 过滤文本
 * @param {Object} data - 节点数据
 * @returns {boolean} 是否显示该节点
 */
const filterNode = (value, data) => {
  if (!value) return true
  return data.title.includes(value)
}
/**获取知识树数据
 * 
 * @async
 * @returns {Promise<void>}
 * 
 * JSON.stringify(value[, replacer][, space])
 * value：要序列化的对象。
   replacer（可选）：用于过滤或转换结果的函数，或者一个数组，用来指定要包含的属性名。如果为 null 或省略，则不进行过滤。
   space（可选）：控制缩进的空白，用于美化输出的格式。
 */
const fetchTreeData = async () => {
  try {
    const response = await knowledgeTreeService.getTree()
    // console.log('获取知识树原始数据:', JSON.stringify(response.data, null, 2)) //上面有解释 2：表示每个层级的缩进用两个空格，这样生成的字符串会更容易阅读。
    treeData.value = response.data?.data || []

    //console.log("当前获取到的数据：", treeData.value) // 测试用的，可以删除
    //console.log('更新后的treeData:', JSON.stringify(treeData.value, null, 2))
  } catch (error) {
    if (error.code === 'ECONNABORTED') {
      ElMessage.error('请求超时，请检查网络连接或稍后再试')
    } else {
      ElMessage.error('获取知识树失败')
    }
    console.error('获取知识树失败:', error)
  }
}
/**节点点击事件处理
 * 
 * @param {Object} data - 节点数据
 */
const handleNodeClick = (data) => {
//  console.log('点击节点:', data)
}
/**添加根节点方法
 * 
 */
const addRootNode = () => {
  dialogType.value = 'add'
  nodeForm.title = ''
  nodeForm.description = ''
  nodeForm.parentId = null
  currentNode.value = null
  dialogVisible.value = true
}
/**添加子节点方法
 * @param {Object} data - 父节点数据
 */
const addChildNode = (data) => {
  dialogType.value = 'add'
  nodeForm.title = ''
  nodeForm.description = ''
  nodeForm.parentId = data.id
  currentNode.value = data
  dialogVisible.value = true
}
/**编辑节点方法
 * @param {Object} data - 要编辑的节点数据
 */
const editNode = (data) => {
  dialogType.value = 'edit'
  nodeForm.title = data.title
  nodeForm.description = data.description || ''
  currentNode.value = data
  dialogVisible.value = true
}
/**删除节点方法
 * @param {Object} node - 树节点对象
 * @param {Object} data - 节点数据
 */
const removeNode = (node, data) => {
  pendingDeleteNode.value = data
  deleteDialogVisible.value = true
}
/**处理删除确认
 * @async
 * @returns {Promise<void>}
 */
const handleDeleteConfirm = async () => {
  try {
    await knowledgeTreeService.deleteNode(pendingDeleteNode.value.id)
    ElMessage.success('删除成功')
    fetchTreeData()
    deleteDialogVisible.value = false
  } catch (error) {
    ElMessage.error('删除失败')
    console.error('删除失败:', error)
  }
}
/**处理删除取消
 * 
 */
const handleDeleteCancel = () => {
  deleteDialogVisible.value = false
  ElMessage.info('已取消删除')
}
/**提交节点表单
 * @async
 * @returns {Promise<void>}
 */
const submitNodeForm = async () => {
  if (!nodeForm.title.trim()) {
    ElMessage.warning('节点名称不能为空')
    return
  }

  try {
    if (dialogType.value === 'add') {
      await knowledgeTreeService.createNode({
        title: nodeForm.title,
        description: nodeForm.description,
        parentId: nodeForm.parentId
      })
      ElMessage.success('添加成功')
    } else {
      await knowledgeTreeService.updateNode(
        currentNode.value.id,
        {
          title: nodeForm.title,
          description: nodeForm.description
        }
      )
      ElMessage.success('更新成功')
    }
    dialogVisible.value = false
    fetchTreeData()
  } catch (error) {
    ElMessage.error(dialogType.value === 'add' ? '添加失败' : '更新失败')
    console.error('操作失败:', error)
  }
}

/**拖拽开始事件处理
 * @param {Object} node - 被拖拽的节点
 * @param {Event} ev - 事件对象
 */
const handleDragStart = (node, ev) => {
 // console.log('开始拖拽', node.data.title)
  ev.dataTransfer.setData('application/json', JSON.stringify({
    title: node.data.title,
    id: node.data.id
  }))
  ev.dataTransfer.effectAllowed = 'copy'
}

/**拖拽进入节点事件处理
 * @param {Object} draggingNode - 被拖拽的节点
 * @param {Object} dropNode - 目标节点
 * @param {Event} ev - 事件对象
 */
const handleDragEnter = (draggingNode, dropNode, ev) => {
//  console.log('拖拽', draggingNode.data.title, '进入:', dropNode.data.title, "将要成为子节点")
}

/**拖拽离开节点事件处理
 * @param {Object} draggingNode - 被拖拽的节点
 * @param {Object} dropNode - 目标节点
 * @param {Event} ev - 事件对象
 */
const handleDragLeave = (draggingNode, dropNode, ev) => {
 // console.log('拖拽', draggingNode.data.title, '离开', dropNode.data.title)
}

/**拖拽经过节点事件处理
 * @param {Object} draggingNode - 被拖拽的节点
 * @param {Object} dropNode - 目标节点
 * @param {Event} ev - 事件对象
 */
const handleDragOver = (draggingNode, dropNode, ev) => {
  //console.log('当前以',dropNode.data.title, "为", draggingNode.data.title,"参考点" )
}

/**拖拽结束事件处理
 * @param {Object} draggingNode - 被拖拽的节点
 * @param {Object} dropNode - 目标节点
 * @param {string} dropType - 放置类型 ('before', 'after', 'inner')
 * @param {Event} ev - 事件对象
 */
const handleDragEnd = (draggingNode, dropNode, dropType, ev) => {
  //console.log("释放", draggingNode.data.title, '到', dropNode && dropNode.data.title, '的', dropType)
}

/**处理拖拽完成（不是成功，仅仅是拖拽完成）时逻辑
 * 
 */
import { fetchWithRetry } from '@/composables/knowledgeTree/fetchWithRetry'; // 引入自定义的fetchWithRetry函数用于重试
const handleDrop = async (draggingNode, dropNode, dropType, ev) => {
  if (window.__dragging__) return
  window.__dragging__ = true

  try {
    // console.log('移动节点参数:', {
    //   nodeId: draggingNode.data.id,
    //   dropNodeId: dropNode?.data?.id || null,
    //   dropType
    // })

    const response = await knowledgeTreeService.moveNode(
      draggingNode.data.id,
      {
        dropNodeId: dropNode?.data?.id || null,
        dropType
      }
    )
    //console.log('移动节点响应:', response.data)

    ElMessage.success({
      message: '节点移动成功',
      duration: 2000,
      showClose: true
    })

    // 强制刷新树组件的技巧：
    // 1. 深拷贝当前树数据到临时变量
    // 2. 清空数据触发组件重新渲染
    // 3. 等待DOM更新完成
    // 4. 恢复数据触发二次渲染
    // 这样能确保Element Plus树组件正确更新视图状态
    /**
    这段代码是Vue中强制刷新组件的一种技巧，主要用于解决Element Plus树组件在数据更新后视图不自动刷新的问题

     具体步骤分析：

      const temp = [...treeData.value]  // 1. 深拷贝当前树数据
      treeData.value = []              // 2. 清空数据触发组件重新渲染
      await nextTick()                 // 3. 等待DOM更新完成
      treeData.value = temp            // 4. 恢复数据，触发二次渲染

      必要性说明：
      1.Element Plus的树组件有时无法检测到数据结构的深层变化
      2.拖拽操作后，组件的内部状态可能没有正确更新
      3.直接修改数据可能不会触发视图更新
      4.这种"先清空再恢复"的方式能确保组件完全重新渲染

      nextTick()的作用：
      1.确保DOM更新队列完成后再执行下一步
      2.避免直接连续赋值导致的渲染问题
      3.给Vue的响应式系统一个"喘息"的机会

      为什么不能直接赋值？
      1.Vue的响应式系统可能无法检测到数组内部变化
      2.树组件维护了自己的内部状态，需要完全重置
      3.直接修改可能不会触发组件的更新机制
      
      这个模式是处理复杂组件更新问题的常见解决方案，特别是在需要强制刷新组件状态时非常有效。
    */
    const temp = [...treeData.value]
    treeData.value = []
    await nextTick()
    treeData.value = temp

    await fetchWithRetry(fetchTreeData, 2)
  } catch (error) {
    console.error('[节点移动失败]', error)
    ElMessage.error({
      message: `移动失败: ${error.response?.data?.message || error.message}`,
      duration: 3000,
      showClose: true
    })
    await fetchTreeData()
  } finally {
    window.__dragging__ = false
  }
}


/**控制节点能否被放置
 * 
 * @param {Object} draggingNode - 被拖拽的节点
 * @param {Object} dropNode - 目标节点
 * @param {string} type - 放置类型 ('before', 'after', 'inner')
 * @returns {boolean}
 */
const allowDrop = (draggingNode, dropNode, type) => {
  if (draggingNode.id === dropNode.id) return false // 节点不能拖拽到自己本身

  const isChild = (parent, child) => {  //在这里把parent传入的是正在拖拽的节点，child传入的是参考（目标）节点（将要被拖入一个子节点的节点）
    if (!parent.childNodes) return false // 为了确保不把父节点拖到子节点中，那么只需要检查正在拖的这个节点有没有子节点就行了，parent.childNodes是正在拖拽到节点的子节点，如果没有那就是“！0”，也就代表着真，如果真则返回false。最后一行程序还会取反。
    return parent.childNodes.some(node =>  //  判断当前节点是否是目标节点的子节点
      node.id === child.id || isChild(node, child)) // 如果当前节点是目标节点的子节点，则返回true
  }

  return !(type === 'inner' && isChild(draggingNode, dropNode))  // 如果拖拽类型为内部，并且拖拽节点是目标节点的子节点，则返回false，否则返回true
}

/**控制节点能否被拖拽
 * 
 * @param {Object} draggingNode - 被拖拽的节点
 * @returns {boolean}
 */
const allowDrag = (draggingNode) => true

/** 组件挂载时获取树数据
 *
 */
onMounted(() => {
  fetchTreeData()
})
</script>

<style scoped>
.left-panel {
  flex: 0 0 25%;
  background: white;
  border-radius: 8px;
  padding: 0 15px 15px 15px;
  box-shadow: 0 2px 8px 0 rgba(0,0,0,0.1);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  height: 100%;
}

.left-panel h2 {
  font-size: 1.3rem;
  font-weight: 600;
  color: #333;
  margin: 15px 0;
  padding-bottom: 12px;
  border-bottom: 1px solid #eee;
}

.operation-area {
  margin-bottom: 15px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.operation-area .filter-input {
  flex: 1;
  min-width: 120px;
}

.knowledge-tree {
  border: 1px solid #ebeef5;
  border-radius: 6px;
  padding: 10px;
  background: #fafafa;
  flex: 1;
  overflow-y: auto;
}

.knowledge-tree :deep(.el-tree-node) {
  margin: 2px 0;
}

.knowledge-tree :deep(.el-tree-node__content) {
  padding: 1px 8px;
  border-radius: 4px;
  transition: all 0.2s ease;
}

.knowledge-tree :deep(.el-tree-node__content:hover) {
  background-color: #f0f7ff;
}

.custom-tree-node {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  padding: 1px 0;
}

.custom-tree-node span:first-child {
  font-weight: 500;
  color: #333;
}

.node-actions {
  display: flex;
  gap: 6px;
  margin-left: 6px;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.custom-tree-node:hover .node-actions {
  opacity: 1;
}

.node-action {
  cursor: pointer;
  transition: all 0.3s;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 3px;
}

.node-action:hover {
  background-color: rgba(0, 0, 0, 0.05);
  transform: scale(1.1);
}

:deep(.el-dialog) {
  border-radius: 8px;
  overflow: hidden;
}

:deep(.el-dialog__header) {
  background: #f5f7fa;
  padding: 15px 20px;
  border-bottom: 1px solid #eee;
}

:deep(.el-dialog__title) {
  font-size: 1.1rem;
  font-weight: 600;
  color: #333;
}

:deep(.el-dialog__body) {
  padding: 20px;
}

:deep(.el-dialog__footer) {
  padding: 12px 20px;
  background: #f5f7fa;
  border-top: 1px solid #eee;
}

.dialog-footer {
  text-align: right;
}

@media (max-width: 1200px) {
  .left-panel {
    padding: 0 12px 12px 12px;
  }
}
</style>
